const acorn = require("acorn");
const walk = require("acorn-walk");
const fs = require("fs");
const path = require("path");

const scanDirectory = (dir) => {
  const files = fs.readdirSync(dir);
  for (const file of files) {
    const filePath = path.join(dir, file);
    const stat = fs.statSync(filePath);
    if (stat.isDirectory()) {
      scanDirectory(filePath);
    } else if (file.endsWith(".wxml")) {
      extractPicks(filePath);
    }
  }
};

const extractPicks = (filePath) => {
  const content = fs.readFileSync(filePath, "utf-8");
  const regex = /{{(.*?)}}/g;
  const picks = new Set();
  let match;
  while ((match = regex.exec(content)) !== null) {
    const code = match[1].trim();
    if (code) {
      const variables = extractAccessExpressions(code);
      variables.forEach((v) => picks.add(v));
    }
  }
  const outputPath = path.join(path.dirname(filePath), "pick.ts");
  let outputContent = "// generated by `npm run pick`, do not edit manually\n";
  if (picks.size > 0) {
    outputContent += "/* eslint-disable */\n";
  }
  outputContent += `export const PICKS: string[] = ${JSON.stringify(
    Array.from(picks).sort(),
    null,
    2,
  )};\n`;
  fs.writeFileSync(outputPath, outputContent, "utf-8");
};

function containsType(ancestors, type) {
  return ancestors.slice(0, -1).some((x) => x.type === type);
}

const extractAccessExpressions = (code) => {
  const variables = new Set();
  try {
    const ast = acorn.parse(code, { ecmaVersion: "latest" });
    walk.ancestor(ast, {
      Identifier(node, ancestors) {
        if (containsType(ancestors, "MemberExpression")) {
          return;
        }
        for (let ancestor of ancestors) {
          if (ancestor.type === "CallExpression" && ancestor.callee?.name === node.name) {
            return;
          }
        }
        variables.add(node.name);
      },
      MemberExpression(node, ancestors) {
        if (containsType(ancestors, "MemberExpression")) {
          return;
        }
        const member = getMemberExpressionString(node);
        variables.add(member);
      },
    });
  } catch (error) {
    console.error(`Error parsing code: "${code}"`, error);
  }
  return variables;
};

const getMemberExpressionString = (node) => {
  if (node.computed) {
    return node.object ? getMemberExpressionString(node.object) : node.name;
  }
  if (node.object && node.object.computed) {
    return getMemberExpressionString(node.object);
  }
  if (node.object && node.property) {
    return `${getMemberExpressionString(node.object)}.${node.property.name}`;
  }
  return node.name;
};

const srcDir = path.join(__dirname, "src");
scanDirectory(srcDir);
